package api

import (
	"context"
	"github.com/gin-gonic/gin"
	"github.com/paulmach/orb"
	"github.com/paulmach/orb/geo"
	"go.uber.org/zap"
	"gorm.io/gorm"
	"seat-service/initialization"
	"seat-service/model"
	"seat-service/model/dto"
	"seat-service/response"
	service "seat-service/service/impl"
	"seat-service/work"
	"strconv"
	"time"
)

var (
	reserveService service.ReserveService
	campusService  service.CampusService
	aheadMinutes   = 10 * time.Minute
	delayMinutes   = 10 * time.Minute
	distance       = 50.0

	clickReserveService service.ClickReserveService
	cancelService       service.CancelService
)

type ReserveApi struct {
}

// SignIn 签到
func (*ReserveApi) SignIn(c *gin.Context) {
	par := &dto.SignInParam{}
	if err := c.ShouldBind(par); err != nil {
		initialization.SeatLogger.Error("c.ShouldBind(par) failed ", zap.Error(err))
		resp.Fail(c, response.CodeParamError, nil)
		return
	}
	// 判断距离
	building, err := buildingService.GetByClassroomId(par.ClassroomID)
	if err != nil {
		initialization.SeatLogger.Error("campusService.Get(par.ClassroomID) failed ", zap.Error(err))
		resp.Fail(c, response.CodeServerBusy, nil)
		return
	}
	pointP := orb.Point{par.Longitude, par.Latitude}
	pointC := orb.Point{building.Longitude, building.Latitude}

	s := geo.DistanceHaversine(pointP, pointC)
	if s >= distance {
		resp.Fail(c, response.CodeOutOfRange, nil)
		return
	}

	//申诉签到
	id := par.StudentID
	exists, err := initialization.Redis.Exists(context.Background(), "appeal"+strconv.Itoa(int(id))).Result()
	if err != nil {
		resp.Fail(c, response.CodeServerBusy, nil)
		return
	}
	if exists == 1 {
		initialization.Redis.Del(context.Background(), "appeal"+strconv.Itoa(int(id)))
		response.Success(c, response.CodeSuccess, nil)
		return
	}

	studentId, _ := c.Get("id")
	// 找出预约记录
	que := model.Reserve{
		StudentId:            studentId.(uint),
		ClassroomID:          par.ClassroomID,
		SeatId:               par.SeatID,
		AppointmentTimeStart: par.Time,
	}
	reserves, err := reserveService.Query(que)
	if err != nil {
		initialization.SeatLogger.Error("reserveService.Query(que) failed ", zap.Error(err))
		resp.Fail(c, response.CodeServerBusy, nil)
		return
	}
	// 判断是否有匹配时间预约记录
	reserve := model.Reserve{}
	if len(reserves) == 0 {
		resp.Fail(c, response.CodeReserveIsEmpty, nil)
		return
	}
	for _, v := range reserves {
		duration := par.Time.Sub(v.AppointmentTimeStart)
		if duration <= delayMinutes && duration >= -aheadMinutes { // 提前或迟到10分钟打卡
			reserve = v
			continue
		}
	}
	if reserve.ReserveStatus == 1 { // 重复签到
		resp.Fail(c, response.CodeReSignIn, nil)
		return
	}
	if reserve.ID == 0 { // 没有符合签到时间的记录
		resp.Fail(c, response.CodeNoFixedTimeSignIn, nil)
		return
	}
	reserve.ReserveStatus = 1
	if err := reserveService.Update(reserve); err != nil {
		initialization.SeatLogger.Error("reserveService.Update(reserve) failed", zap.Error(err))
		resp.Fail(c, response.CodeReSignIn, nil)
		return
	}
	//拿到预约开始时间 学生id
	appointStartTime := reserve.AppointmentTimeStart
	uuid := appointStartTime.Format("2006-01-02 15:04")
	err, boolIsExist := reserveService.DeleteRedisReserve(c, int(studentId.(uint)), uuid)
	if err != nil {
		initialization.SeatLogger.Error("reserveService.DeleteRedisReserve(c, int(studentId.(uint)), uuid) is error", zap.Error(err))
		resp.Fail(c, response.CodeSignFail, nil)
		return
	}
	if boolIsExist {
		//成功签到
		resp.Success(c, response.CodeSuccess, nil)
		return
	}
	//签到超时
	resp.Fail(c, response.CodeSignOverTime, nil)
}

func (*ReserveApi) GetReserve(c *gin.Context) {
	par, _ := c.Get("id")
	id := par.(uint)
	data, err := reserveService.Get(id)
	if err != nil {
		initialization.SeatLogger.Error("reserveService.Get(id) failed ", zap.Error(err))
		resp.Fail(c, response.CodeServerBusy, nil)
		return
	}
	resp.Success(c, response.CodeSuccess, data)
}

func (*ReserveApi) Save(c *gin.Context) {
	id, _ := c.Get("id")

	//param := &dto.ParamReserveSave{}
	param := &dto.ParamReserveSaveStamp{}
	if err := c.ShouldBind(param); err != nil {
		resp.Fail(c, response.CodeParamError, nil)
		return
	}
	// 开启事务
	tx := initialization.DB.Begin()
	for i := range param.Times {
		par := &model.Reserve{
			Model:                gorm.Model{ID: param.ID},
			SeatId:               param.SeatID,
			StudentId:            id.(uint),
			ClassroomID:          param.ClassroomID,
			AppointmentTimeStart: time.Unix(param.Times[i].Start, 0),
			AppointmentTimeEnd:   time.Unix(param.Times[i].End, 0),
		}
		// 判断预约时间内是否预约过其他座位
		if tmps, err := reserveService.GetClash(par.StudentId, par.AppointmentTimeStart, par.AppointmentTimeEnd); err != nil || len(tmps) != 0 {
			if err != nil {
				initialization.SeatLogger.Error("reserveService.ReserveCount(par) failed ", zap.Error(err))
				resp.Fail(c, response.CodeServerBusy, nil)
				tx.Rollback()
			}
			if len(tmps) != 0 {
				resp.Fail(c, response.CodeTimeExist, nil)
				tx.Rollback()
			}
			return
		}
		// 单个座位预约时间不能超过16小时
		t := int(par.AppointmentTimeEnd.Sub(par.AppointmentTimeStart).Minutes())
		if ok := reserveService.GetDuration(par.StudentId, par.SeatId, t); !ok {
			resp.Fail(c, response.CodeOutOfTime, nil)
			tx.Rollback()
			return
		}

		// 判断自己是否预约过
		if err := reserveService.GetByReserve(par); err != nil {
			if err != gorm.ErrRecordNotFound {
				initialization.SeatLogger.Error("reserveService.ReserveCount(par) failed ", zap.Error(err))
				resp.Fail(c, response.CodeServerBusy, nil)
				tx.Rollback()
				return
			}
		}
		if par.ID != 0 {
			resp.Fail(c, response.CodeReserveExist, nil)
			tx.Rollback()
			return
		}
		// 该位置是否有人预约
		count, err := reserveService.ReserveCount(par)
		if err != nil {
			initialization.SeatLogger.Error("reserveService.ReserveCount(par) failed ", zap.Error(err))
			resp.Fail(c, response.CodeServerBusy, nil)
			tx.Rollback()
			return
		}
		if count > 0 {
			resp.Fail(c, response.CodeAlreadyReserve, nil)
			tx.Rollback()
			return
		}
		seat, _ := seatService.GetByIdWithTransaction(tx, par.SeatId)
		if seat.ID == 0 {
			resp.Fail(c, response.CodeSeatNoExit, nil)
			tx.Rollback()
			return
		}
		//if seat.SeatReserve != 0 {
		//	resp.Fail(c, response.CodeSeatStop, nil)
		//	tx.Rollback()
		//	return
		//}
		r, err := reserveService.CreateWithTransaction(tx, par)
		if err != nil {
			initialization.SeatLogger.Error("reserveService.CreateWithTransaction(tx,par) failed ", zap.Error(err))
			resp.Fail(c, response.CodeServerBusy, nil)
			tx.Rollback()
			return
		}
		//预约成功 将reserve+id 需要签到的最晚时间为value 存在redis中
		//获取预约成功的开始时间
		reserveSuccessTime := par.AppointmentTimeStart
		err = reserveService.SaveRedisReserve(r, c, par.StudentId, reserveSuccessTime)
		if err != nil {
			initialization.SeatLogger.Error("reserveService.SaveRedisReserve(c , par.StudentId , reserveSuccessTime) is error", zap.Error(err))
			resp.Fail(c, response.CodeServerBusy, nil)
			tx.Rollback()
			return
		}
	}
	tx.Commit()

	resp.Success(c, response.CodeSuccess, nil)
}

func (*ReserveApi) CheckStudentStatus(context *gin.Context) {
	//拿到当前登录的学生的id
	studentID, ok := context.Get("id")
	if !ok {
		resp.Fail(context, response.CodeParamError, nil)
		return
	}
	student, err := reserveService.CheckStudentStatus(studentID.(uint))
	if err != nil {
		if err.Error() == "StudentISViolate" {
			resp.Fail(context, response.CodeStudentIsViolate, student)
			return
		}
		resp.Fail(context, response.CodeServerBusy, nil)
	}
	resp.Success(context, response.CodeSuccess, nil)
}

func (*ReserveApi) Renewal(c *gin.Context) {
	var renewal dto.RenewalDto
	err := c.ShouldBindJSON(&renewal)
	id, _ := c.Get("id")
	if err != nil {
		resp.Fail(c, response.CodeParamError, nil)
		return
	}
	reserves, err := reserveService.Renewal(id.(uint), renewal.Start, renewal.End)
	if err != nil && reserves == nil {
		if err.Error() == "0" {
			resp.Fail(c, response.CodeRenewal, nil)
		} else {
			resp.Fail(c, response.DatabaseException, nil)
		}
		return
	}
	if err == nil && reserves != nil {
		ren := make([]dto.ParamReserveSaveTime, 0)
		for _, v := range reserves {
			r := dto.ParamReserveSaveTime{
				Start: v.AppointmentTimeStart,
				End:   v.AppointmentTimeEnd,
			}
			ren = append(ren, r)
		}
		resp.Fail(c, response.CodeRenewalTime, ren)
		return
	}
	resp.Success(c, response.CodeSuccess, nil)
}

func (*ReserveApi) Appeal(c *gin.Context) {
	capture := c.Query("seat_id")

	my, _ := c.Get("id")
	//座位id
	id, _ := strconv.Atoi(capture)
	//发起人学生id
	myID, _ := strconv.Atoi(strconv.Itoa(int(my.(uint))))

	/*
		1.对这个座位发起申诉
		2.给目标id推送微信提醒（从座位id找预约单中的用户），需要再次签到
		3.发出提醒后存入redis并设置过期时间，12分钟
		4.如果预约中的用户此时完成签到则删除记录
		5.开启协程在10分钟时检查预约中的用户id记录是否存在。如果存在则告知申诉发起人，申诉成功，并解放该座位。如果失败则告知 发起人申诉失败
	*/
	studentID, err := reserveService.AppealQueryUserID(uint(id))
	if err != nil {
		response.Fail(c, response.CodeServerBusy, nil)
		return
	}
	if studentID == 0 {
		response.Fail(c, response.NoReserve, nil)
	}
	st, err := reserveService.AppealQueryStudent(studentID)
	if err != nil {
		response.Fail(c, response.CodeServerBusy, nil)
		return
	}
	seatNum, err := reserveService.AppealQuerySeatNum(uint(id))
	if err != nil {
		return
	}

	dataMap := map[string]string{
		"name6":  st.Name,
		"thing4": seatNum,
		"thing7": "该座位以被申诉，请在十分钟之内完成签到",
	}
	err = work.PushWX("xMwyPrJPdglosUnvtq1JUdtpesQ5do0sb1aSuzmAdYU", "", st.Openid, work.InitFormat(dataMap), "formal", "zh_CN")
	if err != nil {
		response.Fail(c, response.CodeServerBusy, nil)
		return
	}
	err = initialization.Redis.Set(context.Background(), "appeal"+strconv.Itoa(int(studentID)), 1, time.Minute*12).Err()
	if err != nil {
		response.Fail(c, response.CodeServerBusy, nil)
		return
	}
	go func() {
		time.Sleep(time.Minute * 10)
		result, err := initialization.Redis.Exists(context.Background(), "appeal"+strconv.Itoa(int(studentID))).Result()
		if err != nil {
			return
		}
		student, err := reserveService.AppealQueryStudent(uint(myID))
		if err != nil {
			return
		}
		if result == 0 {
			//被申诉人已经签到
			dataMap0 := map[string]string{
				"thing1": "申诉目标已完成签到",
				"thing5": "申诉失败",
			}
			err := work.PushWX("wFxzFnofISCKB6T7Puu3X6TKcJfoPkAGuFmhqB91OI4", "", student.Openid, work.InitFormat(dataMap0), "formal", "zh_CN")
			if err != nil {
				return
			}
		} else {
			//被申诉人没有签到
			err := reserveService.AppealSuccess(uint(id))
			if err != nil {
				return
			}
			dataMap1 := map[string]string{
				"thing1": "申诉目标未完成签到",
				"thing5": "申诉成功",
			}
			err = work.PushWX("wFxzFnofISCKB6T7Puu3X6TKcJfoPkAGuFmhqB91OI4", "", student.Openid, work.InitFormat(dataMap1), "formal", "zh_CN")
			if err != nil {
				return
			}
		}
	}()
}

// GetOptionalSeats 一键预约
func (*ReserveApi) GetOptionalSeats(c *gin.Context) {
	// 1.获取参数：校区id、楼栋id、教室id、预约时长、预约开始时间
	p := &dto.ParamClickReserve{}
	if err := c.ShouldBindJSON(p); err != nil {
		resp.Fail(c, response.CodeServerBusy, nil)
		return
	}
	// 2.处理参数。找出教室中符合可用预约时间的位置
	startTime := p.StartTime
	endTime := startTime.Add(time.Duration(p.Timeslot) * time.Hour)
	// 获取该教室全部可用位置信息
	seats, err := clickReserveService.GetOptionalSeats(p.CampusId, p.BuildingId, p.ClassroomId)
	if err != nil {
		return
	}
	var data []*dto.ClickReserve
	for i := 0; i < len(seats); i++ {
		// 获取未预约的位置信息
		if seats[i].SeatReserve == 0 {
			data = append(data, seats[i])
		}
		// 获取已预约但和用户预约时间不冲突的位置信息
		if seats[i].SeatReserve == 1 {
			one, err := clickReserveService.GetOptionalSeatsOne(seats[i].ID, startTime, endTime)
			if err != nil {
				initialization.SeatLogger.Error("mysql.GetOptionalSeatsOne() failed", zap.Error(err))
				resp.Fail(c, response.CodeServerBusy, nil)
				return
			}
			if one {
				data = append(data, seats[i])
			}
		}
		// 获取已使用但和用户预约时间不冲突的位置信息
		if seats[i].SeatReserve == 2 {
			two, err := clickReserveService.GetOptionalSeatsTwo(seats[i].ID, startTime)
			if err != nil {
				initialization.SeatLogger.Error("mysql.GetOptionalSeatsTwo() failed", zap.Error(err))
				resp.Fail(c, response.CodeServerBusy, nil)
				return
			}
			if two {
				data = append(data, seats[i])
			}
		}
	}
	// 3.返回响应
	response.Success(c, response.CodeSuccess, data)
}

// CancelReserve 取消预约
func (*ReserveApi) CancelReserve(c *gin.Context) {
	var cr dto.Cancel
	err := c.ShouldBindJSON(&cr)
	if err != nil {
		resp.Fail(c, response.DataInWrongFormat, nil)
		return
	}
	numId, _ := strconv.Atoi(cr.ID)
	err = cancelService.CancelReserve(uint(numId))
	if err != nil {
		resp.Fail(c, response.DatabaseException, nil)
		return
	}
	work.StopPush(cr.ID)
	resp.Success(c, response.CodeSuccess, nil)
}

// CancelUse 取消使用
func (*ReserveApi) CancelUse(c *gin.Context) {
	var cu dto.Cancel
	err := c.ShouldBindJSON(&cu)
	if err != nil {
		resp.Fail(c, response.DataInWrongFormat, nil)
		return
	}
	numId, _ := strconv.Atoi(cu.ID)
	err = cancelService.CancelUse(uint(numId))
	if err != nil {
		resp.Fail(c, response.DatabaseException, nil)
		return
	}
	resp.Success(c, response.CodeSuccess, nil)
}
